﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Reflection;

namespace CodeRun.Util
{
    /// <summary>
    /// Assembly Resolved Configuration
    /// </summary>
    internal class AssemblyResolverConfiguration // : IValidatableConfiguration
    {
        /// <summary>
        /// Assembly search path
        /// </summary>
        [DefaultSettingValue("")]
        public string[] BinPath { get; set; }

        public void ValidateConfiguration()
        {
            foreach (string s in BinPath)
            {
                if (s.IsNullOrEmpty())
                    continue;
                if (!Directory.Exists(s))
                    throw new Exception("A Folder ( " + s +
                                        ") that was specified in CodeRun.AssemblyResolver.BinPath was not found");
            }
        }
    }

    public static class AssemblyResolver
    {
        private static readonly Dictionary<string, Assembly> AssemblyCache = new Dictionary<string, Assembly>();
        private static readonly HashSet<string> BinPath;
        private static AppDomain _Domain;

        static AssemblyResolver()
        {
            BinPath = new HashSet<string>();
        }

        private static AppDomain Domain
        {
            get
            {
                if (_Domain == null)
                    _Domain = AppDomain.CurrentDomain;
                return _Domain;
            }
        }

        //public static void LoadConfiguration()
        //{
        //  LoadConfiguration(ConfigurationHelper.LoadConfigurationSet<AssemblyResolverConfiguration>().BinPath);
        //}

        public static void LoadConfiguration(string[] BinPath)
        {
            if (BinPath.IsNotNullOrEmpty())
                RegisterBinPath(BinPath);
        }

        public static string[] GetBinPaths()
        {
            return BinPath.ToArray();
        }

        private static string[] GetPaths(string paths)
        {
            string[] pathArray =
                paths.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries).Select(
                    t => new DirectoryInfo(t.Trim()).FullName).ToArray();
            return pathArray;
        }

        public static void RegisterBinPath(string paths)
        {
            RegisterBinPath(GetPaths(paths));
        }

        public static void RegisterBinPath(string[] paths)
        {
            foreach (string path in paths.Where(p => p.IsNotNullOrEmpty()))
            {
                if (BinPath.Add(path) && BinPath.Count == 1)
                {
                    Domain.AssemblyResolve += CurrentDomain_AssemblyResolve;
                }
            }
        }

        public static void UnRegisterBinPath(string paths)
        {
            if (BinPath.Count > 0)
            {
                Domain.AssemblyResolve -= CurrentDomain_AssemblyResolve;
            }
            BinPath.Clear();
        }

        private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            Assembly asm;
            if (!AssemblyCache.TryGetValue(args.Name, out asm))
            {
                string name = args.Name;
                int index = name.IndexOf(",");
                if (index > 0)
                    name = name.Substring(0, index);
                foreach (string path in BinPath)
                {
                    string filename = name + ".dll";
                    string filepath = Path.Combine(path, filename);
                    if (!File.Exists(filepath))
                    {
                        filepath = Path.ChangeExtension(filepath, ".exe");
                    }
                    if (File.Exists(filepath))
                    {
                        asm = Assembly.LoadFrom(filepath);
                        if (asm != null)
                        {
                            AssemblyCache[asm.FullName] = asm;
                            Console.WriteLine("Assembly resolver found assembly " + asm.Location);
                            break;
                        }
                    }
                }
            }
            return asm;
        }
    }
}